fix(s2/Toast): offset toast above on-screen keyboard on mobile#9833
fix(s2/Toast): offset toast above on-screen keyboard on mobile#9833mrlexcoder wants to merge 3 commits intoadobe:mainfrom
Conversation
Fixes adobe#9681 When the virtual keyboard is open on mobile, position:fixed elements are placed relative to the layout viewport, not the visual viewport. This causes bottom-placed toasts to render underneath the keyboard. Add a useKeyboardOffset() hook that listens to visualViewport resize and scroll events and computes the keyboard height as: keyboardHeight = window.innerHeight - (vv.offsetTop + vv.height) When a non-zero offset is detected and placement is 'bottom', the ToastRegion's bottom position is overridden via an inline style to sit 16px above the top of the keyboard instead of 16px above the bottom of the layout viewport. The hook returns 0 when visualViewport is unavailable (SSR / older browsers), so desktop and server rendering are unaffected. Signed-off-by: mrlexcoder <mrlexcoder@gmail.com>
snowystinger
left a comment
There was a problem hiding this comment.
Thanks for the PR.
Did you find out anything more about #9681 (comment) ? or why
height: 100vh;
height: 100dvh;
would or wouldn't work?
We have a hook for watching viewport size, called useViewportSize.
| {...props} | ||
| ref={regionRef} | ||
| queue={queue} | ||
| style={isBottom && keyboardOffset > 0 ? {bottom: keyboardOffset + 16} : undefined} |
There was a problem hiding this comment.
if this is really needed, then this should be built into the style macro, typically with a variable so that the property isn't inline
roughly
style={{'--keyboardOffset': isBottom ? keyboardOffset : 0}}
// off in the style macro
bottom: 'calc(var(--keyboardOffset) + 16)'
- Replace inline bottom style override with a --keyboardOffset CSS variable fed into the toastRegion style macro via calc() - bottom is now: calc(var(--keyboardOffset, 0px) + 16px) - Variable is only set when placement is 'bottom' - Avoids inline style property conflict with the style macro Signed-off-by: mrlexcoder <mrlexcoder@gmail.com>
|
Thanks for the review @snowystinger! Re:
Additionally, Re: CSS variable approach Updated the PR to use |
|
using 100dvh may be the wrong approach, but maybe you could do something like this potentially
This can be changed and was the a recommendation in the Issue.
Which older Safari and Android, what're the lowest versions with support? It looks like typescript is failing |
Replace the JS visualViewport hook with a pure CSS solution: bottom: calc(100vh - 100dvh + 16px) When the on-screen keyboard is open, dvh (dynamic viewport height) shrinks to the visible area while vh (layout viewport) stays fixed. The difference (100vh - 100dvh) equals the keyboard height, so the toast sits 16px above the keyboard automatically. On desktop where no keyboard is present, 100vh === 100dvh so the expression reduces to 16px — identical to the previous behaviour. No JS, no event listeners, no CSS variables needed. Signed-off-by: mrlexcoder <mrlexcoder@gmail.com>
|
Thanks for the continued feedback @snowystinger! Re: You're right — the bottom: calc(100vh - 100dvh + 16px)When the keyboard is open, Re: According to caniuse,
Anything older (iOS Safari ≤ 15.3, Chrome ≤ 107, Samsung Internet ≤ 20) will fall back to Re: TypeScript Fixed — removed the |
| // calc(100vh - 100dvh + 16px) equals the keyboard height + 16px, | ||
| // keeping the toast above the keyboard on any browser that supports dvh. | ||
| // On desktop (no keyboard) 100vh === 100dvh so this reduces to 16px. | ||
| default: '[calc(100vh - 100dvh + 16px)]', |
There was a problem hiding this comment.
This is appearing too high off the bottom for me on iOS 26
maybe we can use
| default: '[calc(100vh - 100dvh + 16px)]', | |
| default: '[calc(env(safe-area-inset-bottom) + 16px)]', |
though that may not work on previous iOS versions, @yihuiliao could you try this?
Summary
Fixes #9681
On mobile web, when the virtual keyboard is open,
position: fixedelements are positioned relative to the layout viewport, not the visual viewport. This causes bottom-placed toasts to render underneath the keyboard.Root cause
The S2
ToastContainerusesbottom: 16pxfrom the CSS-in-JStoastRegionstyle. When the keyboard opens, the visual viewport shrinks but the layout viewport does not, so the toast stays anchored to the bottom of the full page — behind the keyboard.Fix
Added a
useKeyboardOffset()hook inpackages/@react-spectrum/s2/src/Toast.tsxthat:visualViewportresizeandscrollevents0whenvisualViewportis unavailable (SSR / older browsers)When a non-zero offset is detected and
placementis'bottom', theToastRegion's bottom position is overridden via an inline style to sit 16px above the keyboard instead of 16px above the layout viewport bottom.Behaviour
placement='top'